package com.example.mybatis.handler.result;

import com.example.mybatis.config.Config;
import com.example.mybatis.exception.MybatisException;
import com.example.mybatis.handler.type.handler.TypeHandler;
import com.example.mybatis.mapper.MapperResultItemTemplate;
import com.github.houbb.heaven.util.lang.ObjectUtil;
import com.github.houbb.heaven.util.lang.StringUtil;
import com.github.houbb.heaven.util.lang.reflect.ClassUtil;
import com.github.houbb.heaven.util.lang.reflect.ReflectFieldUtil;
import com.github.houbb.heaven.util.lang.reflect.ReflectMethodUtil;
import com.github.houbb.heaven.util.util.CollectionUtil;

import javax.xml.ws.Holder;
import java.lang.reflect.Field;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * 引用对象的处理
 */
public class ObjectRefResultTypeHandler extends AbstractResultTypeHandler {
    /**
     * 静态内部类
     *
     * @since 0.0.10
     */
    private static class Holder {
        private static final ObjectRefResultTypeHandler INSTANCE = new ObjectRefResultTypeHandler();
    }

    public static ObjectRefResultTypeHandler getInstance() {
        return Holder.INSTANCE;
    }

    @Override
    protected Object doBuildResult(ResultHandlerContext context) {
        try {
            //基本信息
            final ResultSet resultSet = context.resultSet();
            //try {
            final ResultSetMetaData metaData = resultSet.getMetaData();
            final Class<?> resultType = context.resultType();
            final Config config = context.config();
            //映射集合
            Map<String, Field> fieldMap = new HashMap<>();
            final List<MapperResultItemTemplate> mapperResultItemTemplates = context.resultMapMapping();
            Map<String, String> metaColumnMapping = buildMetaMapping(mapperResultItemTemplates);
            if (CollectionUtil.isNotEmpty(mapperResultItemTemplates)) {
                //2.1如果不为空，优先以执行的结果映射为
                fieldMap = buildClassFiledMap(resultType, mapperResultItemTemplates);
            } else {
                fieldMap = buildClassFieldMap(resultType);
            }
            //基本类型，非java对象，直接返回即可
            Object instance = config.newInstance(resultType);
            //列数的总和
            int columnCount = metaData.getColumnCount();
            for (int i = 1; i <= columnCount; i++) {
                //这里获取的原始列名称
                String columnName = metaData.getColumnName(i);
                //需要做一个转换
                String mappingColumnName = metaColumnMapping.get(columnName);
                if (StringUtil.isNotEmpty(mappingColumnName)) {
                    columnName = mappingColumnName;
                }
                Field filed = fieldMap.get(columnName);
                if (filed == null) {
                    String msg = String.format("Class %s not found field for column <%s>.", resultType.getName(), columnName);
                    throw new MybatisException(msg);
                }
                Object value = getResult(filed, columnName, resultSet, config);
                //不为null才进行设置
                if (ObjectUtil.isNotNull(value)) {
                    ReflectFieldUtil.setValue(filed, instance, value);
                }
            }
            return instance;
        } catch (SQLException e) {
            throw new MybatisException(e);
        }

      // return null;
    }
    /**
     * 获取对应的结果
     * <p>
     * TODO: 这里其实有几种方式优雅的获取 rs 查询的列信息
     * <p>
     * 1. rs.findColumn() 经过测试，发现会报错，不友好
     * <p>
     * 2. 解析 sql，构建 rs 的字段集合。
     * <p>
     * 3. 根据 {@link ResultSet#getMetaData()} 处理信息
     * <p>
     * 此处暂时使用第三种方式。
     *
     * @param field      字段信息
     * @param columnName 列名称
     * @param rs         结果集
     * @param config     配置信息
     * @return 结果
     * @since 0.0.1
     */
    private Object getResult(Field filed, String columnName, ResultSet resultSet, Config config) {
      Class<?>  type =    filed.getType();
       TypeHandler<?>  typeHandler =   config.getTypeHandler(type);
        try {
            return  typeHandler.getResult(resultSet,columnName);
        } catch (SQLException e) {
            throw new MybatisException(e);
        }
    }
    /**
     * 获取映射字段
     *
     * @return map
     * @since 0.0.9
     */
      private Map<String, Field> buildClassFieldMap(Class<?> resultType) {
      List<Field>  fieldList=     ClassUtil.getAllFieldList(resultType);;
      Map<String,Field> map = new HashMap<>();
      for (Field field : fieldList){
     String fieldName =    field.getName();
      //驼峰转下划线，后期这里需要配置，或者制定朱姐
        String columnName =     StringUtil.camelToUnderline(fieldName);
         map.put(columnName,field);
      }
      return  map;
    }

    private Map<String, Field> buildClassFiledMap(Class<?> resultType, List<MapperResultItemTemplate> mapperResultItemTemplates) {
        Map<String, Field> resultMap = new HashMap<>();

        Map<String, Field> fieldMap = ClassUtil.getAllFieldMap(resultType);
        for (MapperResultItemTemplate mapItem : mapperResultItemTemplates) {
            String propertyName = mapItem.getProperty();
            String columnName = mapItem.getColumn();

            Field field = fieldMap.get(propertyName);
            if(ObjectUtil.isNull(field)) {
                String msg = String.format("Class %s not found field for column <%s>.",
                        resultType.getName(), columnName);
                throw new MybatisException(msg);
            }

            resultMap.put(columnName, field);
        }

        return resultMap;
    }

    private Map<String, String> buildMetaMapping(List<MapperResultItemTemplate> mapperResultItemTemplates) {
        if(CollectionUtil.isEmpty(mapperResultItemTemplates)) {
            return Collections.emptyMap();
        }
        Map<String, String> resultMap = new HashMap<>(mapperResultItemTemplates.size());
        for(MapperResultItemTemplate mapItem : mapperResultItemTemplates) {
            // 这里不够严谨
            // TODO: 后续还是需要调整为根据返回的 metadata 处理
            // 针对不同的数据库，有不同的实现即可。
            String originalColumn = StringUtil.camelToUnderline(mapItem.getProperty());
            resultMap.put(originalColumn, mapItem.getColumn());
        }
        return resultMap;
      }
}
